﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Sio.Mdm.Connector
{
    public abstract class ConnectorBase
    {
        #region Fields
        protected const int __defaultPortSSL = 636;
        protected const int __defaultport = 389;
        protected eLdapDirectoryType _directoryType;
        protected bool _isConnected = false;
        protected bool _isUsingSSL = false;
        protected string _directoryBase;
        protected string _server;
        protected int _port;
        protected string _lastDN;                         // Returned by/when calling Exists()
        #endregion

        #region Properties
        public bool isConnected
        {
            get
            {
                return _isConnected;
            }
        }

        public string directoryBase
        {
            get
            {
                if (String.IsNullOrEmpty(_directoryBase))
                {
                    throw new Exception("Directory base not specified!");
                }
                return _directoryBase;
            }
        }

        public eLdapDirectoryType directoryType
        {
            get
            {
                return _directoryType;
            }
        }

        public bool isUsingSLL
        {
            get
            {
                return _isUsingSSL;
            }
        }
        public string Server
        {
            get
            {
                return _server;
            }
        }
        public int Port
        {
            get
            {
                return _port;
            }
        }
        public string LastDN
        {
            get
            {
                return _lastDN;
            }
        }
        #endregion

        #region Enums
        public enum eLdapDirectoryType
        {
            ActiveDirectory = 0,
            OpenLdap = 1
        }

        public enum eLdapSearchField
        {
            DN,
            CN,
            Username,
            All
        }

        public enum eLdapSearchObject
        {
            user,
            group
        }

        public enum eLdapSearchScope
        {
            oneLevel,
            subtree
        }

        #endregion

        #region Connect
        public void Connect(string server, string userName, string password)
        {
            Connect(server, __defaultport, userName, password, false);
        }

        public void Connect(string server, int port, string userName, string password)
        {
            Connect(server, port, userName, password, false);
        }

        public void Connect(string server, string userName, string password, bool useSSL)
        {
            if (useSSL)
            {
                Connect(server, __defaultPortSSL, userName, password, useSSL);
            }
            else
            {
                Connect(server, __defaultport, userName, password, useSSL);
            }
        }

        public abstract void Connect(string server, int port, string userName, string password, bool useSSL);        
        #endregion

        #region Exists
        public bool Exists(string value, eLdapSearchField searchField)
        {
            return Exists(value, searchField, eLdapSearchScope.subtree, "", eLdapSearchObject.user);
        }

        public bool Exists(string value, eLdapSearchField searchField, eLdapSearchScope searchScope)
        {
            return Exists(value, searchField, searchScope, "", eLdapSearchObject.user);
        }

        public bool Exists(string value, eLdapSearchField searchField, eLdapSearchScope searchScope, string baseDN)
        {
            return Exists(value, searchField, searchScope, baseDN, eLdapSearchObject.user);
        }

        public abstract bool Exists(string value, eLdapSearchField searchField, eLdapSearchScope searchScope, string baseDN, eLdapSearchObject searchObject);
        
        #endregion

        #region GetDiff
        /// <summary>
        /// Each key in data1 is compared to data2. Equal keys are deleted! 
        /// </summary>
        /// <param name="data1"></param>
        /// <param name="data2"></param>
        /// <returns></returns>
        public ldapObject GetDiff(ldapObject data1, ldapObject data2)
        {
            List<string> keys = data1.GetKeys();

            foreach (string key in keys)
            {
                List<object> values1 = data1[key];
                if (data2.Contains(key))
                {
                    // COMPARE
                    List<object> values2 = data2[key];
                    if (values1[0].ToString() == values2[0].ToString())           // TODO: add support for multiple values in same key
                    {
                        data1.Remove(key);
                    }
                }
            }

            return data1;
        }
        #endregion

        #region GetBase
        public static string GetBaseDN(string DN)
        {
            int index = DN.IndexOf(",");
            string baseDN = DN.Remove(0, index + 1);

            return baseDN;
        }

        public static string GetBaseCN(string DN)
        {
            int index = DN.IndexOf("=");
            string baseCN = DN.Remove(0, index + 1);

            index = baseCN.IndexOf(",");
            baseCN = baseCN.Substring(0, index);

            return baseCN;
        }
        #endregion

        #region Private
        protected string GetUserSearchCriteria(eLdapSearchField searchField, string value)
        {
            if (directoryType == eLdapDirectoryType.ActiveDirectory)
            {
                switch (searchField)
                {
                    case eLdapSearchField.CN:
                        return String.Format("(&(objectClass=user)(cn={0}))", value);

                    case eLdapSearchField.DN:
                        return String.Format("(&(objectClass=user)(distinguishedName={0}))", value);

                    case eLdapSearchField.Username:
                        return String.Format("(&(objectClass=user)(sAMAccountName={0}))", value);

                    default:
                        return "";
                }
            }
            else
            {
                switch (searchField)
                {
                    case eLdapSearchField.CN:
                        return String.Format("(&(objectClass=person)(cn={0}))", value);

                    case eLdapSearchField.DN:
                        return String.Format("(&(objectClass=person)(dn={0}))", value);

                    case eLdapSearchField.Username:
                        return String.Format("(&(objectClass=person)(eduPersonPrincipalName={0}))", value);
                    default:
                        return "";
                }
            }
        }

        protected string GetGroupSearchCriteria(eLdapSearchField searchField, string value)
        {
            switch (searchField)
            {
                case eLdapSearchField.CN:
                    return String.Format("(&(objectClass=group) (cn={0}))", value);

                case eLdapSearchField.DN:
                    return String.Format("(&(objectClass=group) (distinguishedName={0}))", value);

                case eLdapSearchField.All:
                    return "(objectClass=group)";

                default:
                    return "";
            }
        }

        protected int GetAtributesCount(ldapObject data)
        {
            return 0;
        }

        protected void PrepareObjectData(ldapObject data)
        {
            if (String.IsNullOrEmpty(data.DN))
            {
                throw new Exception("DN field can not be null or empty");
            }

            if (String.IsNullOrEmpty(data.CN))
            {
                throw new Exception("CN field can not be null or empty");
            }

            if (!data.DN.Contains(data.CN))
            {
                data.DN = string.Format("CN={0},{1}", data.CN, data.DN);
            }
        }        
        #endregion

    }

}
